import { DataManager } from "@/managers/DataManager.js";

import { Game_Unit } from "@/objects/GameUnit.js";
import { Game_Item } from "@/objects/GameItem.js";
import { contains } from "@/utils/Array.js";
import { clamp } from "@/utils/number.js";

export class Game_Party extends Game_Unit {
    static ABILITY_ENCOUNTER_HALF    = 0;
    static ABILITY_ENCOUNTER_NONE    = 1;
    static ABILITY_CANCEL_SURPRISE   = 2;
    static ABILITY_RAISE_PREEMPTIVE  = 3;
    static ABILITY_GOLD_DOUBLE       = 4;
    static ABILITY_DROP_ITEM_DOUBLE  = 5;
    constructor() {
        super();
        this._gold = 0;
        this._steps = 0;
        this._lastItem = new Game_Item();
        this._menuActorId = 0;
        this._targetActorId = 0;
        this._actors = [];
        this.initAllItems();
    }

    initAllItems() {
        this._items = {};
        this._weapons = {};
        this._armors = {};
    }

    exists() {
        return this._actors.length > 0;
    }

    size() {
        return this.members().length;
    }

    isEmpty() {
        return this.size() === 0;
    }

    members() {
        return this.inBattle() ? this.battleMembers() : this.allMembers();
    }

    allMembers() {
        return this._actors.map(function(id) {
            return $gameActors.actor(id);
        });
    }

    battleMembers() {
        return this.allMembers()
        .slice(0, this.maxBattleMembers())
        .filter(actor=>actor.isAppeared());
    }

    maxBattleMembers() {
        return 4;
    }

    leader() {
        return this.battleMembers()[0];
    }

    reviveBattleMembers() {
        this.battleMembers().forEach(function(actor) {
            if (actor.isDead()) {
                actor.setHp(1);
            }
        });
    }

    items() {
        let list = [];
        for (let id in this._items) {
            list.push($dataItems[id]);
        }
        return list;
    }

    weapons() {
        let list = [];
        for (let id in this._weapons) {
            list.push($dataWeapons[id]);
        }
        return list;
    }

    armors() {
        let list = [];
        for (let id in this._armors) {
            list.push($dataArmors[id]);
        }
        return list;
    }

    equipItems() {
        return this.weapons().concat(this.armors());
    }

    allItems() {
        return this.items().concat(this.equipItems());
    }

    itemContainer(item) {
        if (!item) {
            return null;
        } else if (DataManager.isItem(item)) {
            return this._items;
        } else if (DataManager.isWeapon(item)) {
            return this._weapons;
        } else if (DataManager.isArmor(item)) {
            return this._armors;
        } else {
            return null;
        }
    }

    setupStartingMembers() {
        this._actors = [];
        $dataSystem.partyMembers.forEach(function(actorId) {
            if ($gameActors.actor(actorId)) {
                this._actors.push(actorId);
            }
        }, this);
    }

    name() {
        let numBattleMembers = this.battleMembers().length;
        if (numBattleMembers === 0) {
            return '';
        } else if (numBattleMembers === 1) {
            return this.leader().name();
        } else {
            return TextManager.partyName.format(this.leader().name());
        }
    }

    setupBattleTest() {
        this.setupBattleTestMembers();
        this.setupBattleTestItems();
    }

    setupBattleTestMembers() {
        $dataSystem.testBattlers.forEach(function(battler) {
            let actor = $gameActors.actor(battler.actorId);
            if (actor) {
                actor.changeLevel(battler.level, false);
                actor.initEquips(battler.equips);
                actor.recoverAll();
                this.addActor(battler.actorId);
            }
        }, this);
    }

    setupBattleTestItems() {
        $dataItems.forEach(function(item) {
            if (item && item.name.length > 0) {
                this.gainItem(item, this.maxItems(item));
            }
        }, this);
    }

    highestLevel() {
        return Math.max.apply(null, this.members().map(function(actor) {
            return actor.level;
        }));
    }

    addActor(actorId) {
        if (!contains(this._actors,actorId)) {
            this._actors.push(actorId);
            $gamePlayer.refresh();
            $gameMap.requestRefresh();
        }
    }

    removeActor(actorId) {
        if (contains(this._actors,actorId)) {
            this._actors.splice(this._actors.indexOf(actorId), 1);
            $gamePlayer.refresh();
            $gameMap.requestRefresh();
        }
    }

    gold() {
        return this._gold;
    }

    gainGold(amount) {
        this._gold = clamp(this._gold + amount,0, this.maxGold());
    }

    loseGold(amount) {
        this.gainGold(-amount);
    }

    maxGold() {
        return 99999999;
    }

    steps() {
        return this._steps;
    }

    increaseSteps() {
        this._steps++;
    }

    numItems(item) {
        let container = this.itemContainer(item);
        return (container?.[item.id])??0;
    }

    maxItems(item) {
        return 99;
    }

    hasMaxItems(item) {
        return this.numItems(item) >= this.maxItems(item);
    }

    hasItem(item, includeEquip) {
        if (includeEquip === undefined) {
            includeEquip = false;
        }
        if (this.numItems(item) > 0) {
            return true;
        } else if (includeEquip && this.isAnyMemberEquipped(item)) {
            return true;
        } else {
            return false;
        }
    }

    isAnyMemberEquipped(item) {
        return this.members().some(function(actor) {
            return contains(actor.equips(),item);
        });
    }

    gainItem(item, amount, includeEquip) {
        let container = this.itemContainer(item);
        if (container) {
            let lastNumber = this.numItems(item);
            let newNumber = lastNumber + amount;
            container[item.id] = clamp(newNumber,0, this.maxItems(item));
            if (container[item.id] == 0) {
                delete container[item.id];
            }
            if (includeEquip && newNumber < 0) {
                this.discardMembersEquip(item, -newNumber);
            }
            $gameMap.requestRefresh();
        }
    }

    discardMembersEquip(item, amount) {
        let n = amount;
        this.members().forEach(function(actor) {
            while (n > 0 && actor.isEquipped(item)) {
                actor.discardEquip(item);
                n--;
            }
        });
    }

    loseItem(item, amount, includeEquip) {
        this.gainItem(item, -amount, includeEquip);
    }

    consumeItem(item) {
        if (DataManager.isItem(item) && item.consumable) {
            this.loseItem(item, 1);
        }
    }

    canUse(item) {
        return this.members().some(function(actor) {
            return actor.canUse(item);
        });
    }

    canInput() {
        return this.members().some(function(actor) {
            return actor.canInput();
        });
    }

    isAllDead() {
        if (Game_Unit.prototype.isAllDead.call(this)) {
            return this.inBattle() || !this.isEmpty();
        } else {
            return false;
        }
    }

    onPlayerWalk() {
        this.members().forEach(function(actor) {
            return actor.onPlayerWalk();
        });
    }

    menuActor() {
        let actor = $gameActors.actor(this._menuActorId);
        if (!contains(this.members(),actor)) {
            actor = this.members()[0];
        }
        return actor;
    }

    setMenuActor(actor) {
        this._menuActorId = actor?.actorId()??0;
    }

    makeMenuActorNext() {
        let index = this.members().indexOf(this.menuActor());
        if (index >= 0) {
            index = (index + 1) % this.members().length;
            this.setMenuActor(this.members()[index]);
        } else {
            this.setMenuActor(this.members()[0]);
        }
    }

    makeMenuActorPrevious() {
        let index = this.members().indexOf(this.menuActor());
        if (index >= 0) {
            index = (index + this.members().length - 1) % this.members().length;
            this.setMenuActor(this.members()[index]);
        } else {
            this.setMenuActor(this.members()[0]);
        }
    }

    targetActor() {
        let actor = $gameActors.actor(this._targetActorId);
        if (!contains(this.members(),actor)) {
            actor = this.members()[0];
        }
        return actor;
    }

    setTargetActor(actor) {
        this._targetActorId = actor.actorId();
    }

    lastItem() {
        return this._lastItem.object();
    }

    setLastItem(item) {
        this._lastItem.setObject(item);
    }

    swapOrder(index1, index2) {
        let temp = this._actors[index1];
        this._actors[index1] = this._actors[index2];
        this._actors[index2] = temp;
        $gamePlayer.refresh();
    }

    charactersForSavefile() {
        return this.battleMembers().map(function(actor) {
            return [actor.characterName(), actor.characterIndex()];
        });
    }

    facesForSavefile() {
        return this.battleMembers().map(function(actor) {
            return [actor.faceName(), actor.faceIndex()];
        });
    }

    partyAbility(abilityId) {
        return this.battleMembers().some(function(actor) {
            return actor.partyAbility(abilityId);
        });
    }

    hasEncounterHalf() {
        return this.partyAbility(Game_Party.ABILITY_ENCOUNTER_HALF);
    }

    hasEncounterNone() {
        return this.partyAbility(Game_Party.ABILITY_ENCOUNTER_NONE);
    }

    hasCancelSurprise() {
        return this.partyAbility(Game_Party.ABILITY_CANCEL_SURPRISE);
    }

    hasRaisePreemptive() {
        return this.partyAbility(Game_Party.ABILITY_RAISE_PREEMPTIVE);
    }

    hasGoldDouble() {
        return this.partyAbility(Game_Party.ABILITY_GOLD_DOUBLE);
    }

    hasDropItemDouble() {
        return this.partyAbility(Game_Party.ABILITY_DROP_ITEM_DOUBLE);
    }

    ratePreemptive(troopAgi) {
        let rate = this.agility() >= troopAgi ? 0.05 : 0.03;
        if (this.hasRaisePreemptive()) {
            rate *= 4;
        }
        return rate;
    }

    rateSurprise(troopAgi) {
        let rate = this.agility() >= troopAgi ? 0.03 : 0.05;
        if (this.hasCancelSurprise()) {
            rate = 0;
        }
        return rate;
    }

    performVictory() {
        this.members().forEach(function(actor) {
            actor.performVictory();
        });
    }

    performEscape() {
        this.members().forEach(function(actor) {
            actor.performEscape();
        });
    }

    removeBattleStates() {
        this.members().forEach(function(actor) {
            actor.removeBattleStates();
        });
    }

    requestMotionRefresh() {
        this.members().forEach(function(actor) {
            actor.requestMotionRefresh();
        });
    }

    serialize(){
        return {
            gold: this._gold,
            steps: this._steps,
            menuActorId: this._menuActorId,
            targetActorId: this._targetActorId,
            actors: this._actors,
            items: this._items,
            weapons: this._weapons,
            armors: this._armors
        }
    }

    deserialize(data){
        this._gold = data.gold;
        this._steps = data.steps;
        this._menuActorId = data.menuActorId;
        this._targetActorId = data.targetActorId;
        this._actors = data.actors;
        this._items = data.items;
        this._weapons = data.weapons;
        this._armors = data.armors;
    }
}